package com.xforce.doc.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xforce.doc.swagger.XforcceSwagger2MarkupConverter;
import io.github.swagger2markup.Language;
import io.github.swagger2markup.Swagger2MarkupConfig;
import io.github.swagger2markup.Swagger2MarkupConverter;
import io.github.swagger2markup.builder.Swagger2MarkupConfigBuilder;
import io.github.swagger2markup.markup.builder.MarkupLanguage;
import io.swagger.models.*;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.ObjectProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.parser.SwaggerParser;
import io.swagger.util.Json;
import io.swagger.util.Yaml;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

@Component
public class SwaggerUtils {


    private static OkHttpUtil okHttpUtil;

    @Autowired
    public void setOkHttpUtil(OkHttpUtil okHttpUtil) {
        SwaggerUtils.okHttpUtil = okHttpUtil;
    }

    private static String showDocUrl = "http://47.96.172.161:8080/server/index.php?s=/api/item/updateByApi";
    private static String swaggerUiUrl = "http://47.96.172.161:8081/#/";

    private static Swagger2MarkupConfig config;

    static {
        config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.MARKDOWN)
                .withOutputLanguage(Language.ZH)
                .withFlatBody()
                .withGeneratedExamples()
                .build();
    }



    public static void swaggerFile2Zh(String description,String[] removePaths) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        URL url = SwaggerUtils.class.getClassLoader().getResource("public/swagger/api.json");
        JsonNode jsonNode = mapper.readValue(url, JsonNode.class);
        Swagger swagger = new SwaggerParser().read(jsonNode);

        if (StringUtils.isNotBlank(description)){
            swagger.getInfo().setDescription(description);
        }

        if (removePaths.length > 0){
            Map<String, Path> pathMap = swagger.getPaths();
            Arrays.asList(removePaths).forEach(path -> {
                pathMap.remove(path);
            });
        }


        List<Tag> tags = swagger.getTags();
        Map<String, Path> paths = swagger.getPaths();

        List<Tag> tagsZH = new LinkedList<>();
        Map<String ,String> tagsMapper = new HashMap<>();
        tags.forEach( tag -> {
            tagsMapper.put(tag.getName(),tag.getDescription());
            Tag tagZh = new Tag();
            tagZh.setName(tag.getDescription());
            tagZh.setDescription(tag.getDescription());
            tagsZH.add(tagZh);
        } );
        swagger.setTags(tagsZH);


        paths.forEach((k,v) ->{
            doZh(v.getPost(),tagsMapper);
            doZh(v.getGet(),tagsMapper);
            doZh(v.getDelete(),tagsMapper);
            doZh(v.getPut(),tagsMapper);
            doZh(v.getOptions(),tagsMapper);
            doZh(v.getHead(),tagsMapper);
            doZh(v.getPatch(),tagsMapper);
        });

        Json.pretty().writeValue(new File("src/docs/swagger2markup/source/api_zh2.json"), swagger);
        Yaml.pretty().writeValue(new File("src/docs/swagger2markup/source/api_zh2.yaml"), swagger);
    }

    private static void doZh(Operation operation, Map<String, String> tagsMapper){
        if(null!= operation && null != operation.getTags()){
            List<String> zhTags = new LinkedList<>();
            operation.getTags().forEach( tag -> zhTags.add(tagsMapper.get(tag)));
            operation.setTags(zhTags);
            System.err.println(operation.getTags().get(0));
        }
    }

    private static String getExternalDocs(Path path){
        if(null!= path.getPost()){
            return path.getPost().getTags().get(0)+"/"+path.getPost().getOperationId();
        }
        if(null!= path.getGet()){
            return path.getGet().getTags().get(0)+"/"+path.getGet().getOperationId();
        }
        if(null!= path.getHead()){
            return path.getHead().getTags().get(0)+"/"+path.getHead().getOperationId();
        }
        if(null!= path.getDelete()){
            return path.getDelete().getTags().get(0)+"/"+path.getDelete().getOperationId();
        }
        if(null!= path.getOptions()){
            return path.getOptions().getTags().get(0)+"/"+path.getOptions().getOperationId();
        }
        if(null!= path.getPut()){
            return path.getPut().getTags().get(0)+"/"+path.getPut().getOperationId();
        }
        if(null!= path.getPatch()){
            return path.getPatch().getTags().get(0)+"/"+path.getPatch().getOperationId();
        }
        return "";
    }

    private static Map<String,Model> definitions = new HashMap<>();


    /**
     * 将模型与接口文档组合
     * @param swagger
     * @param path
     * @return
     */
    public static String doPathWithParameter(Swagger swagger,Path path){

        StringBuilder builder = new StringBuilder();

        // 请求报文模型处理
        if(path.getPost() != null){
            // POST 请求报文处理
            /*if(StringUtils.equals(path.getPost().getOperationId(),"invoiceVerifyRequestBatch")){
                List<Parameter> postParameters = path.getPost().getParameters();
                postAndPutModelDoc(swagger, builder, postParameters);
                getParAndChangeId(swagger, builder, path.getPost().getResponses());
            }*/
            List<Parameter> postParameters = path.getPost().getParameters();
            postAndPutModelDoc(swagger, builder, postParameters);
            getParAndChangeId(swagger, builder, path.getPost().getResponses());
        }

        if( null != path.getPut() ){
            List<Parameter> putParameters = path.getPut().getParameters();
            postAndPutModelDoc(swagger, builder, putParameters);
            getParAndChangeId(swagger, builder, path.getPut().getResponses());
        }




        // 响应报文处理
        if(null != path.getGet()){
            /*if(StringUtils.equals(path.getGet().getOperationId(),"getSysLogListByTypeAndOpId")){
                getParAndChangeId(swagger, builder, path.getGet().getResponses());
            }*/
            getParAndChangeId(swagger, builder, path.getGet().getResponses());
        }
        if(null!= path.getHead()){
            getParAndChangeId(swagger, builder, path.getHead().getResponses());
        }
        if(null!= path.getDelete()){
            getParAndChangeId(swagger, builder, path.getDelete().getResponses());
        }
        if(null!= path.getOptions()){
            getParAndChangeId(swagger, builder, path.getOptions().getResponses());
        }
        /*if(null!= path.getPut()){
            getParAndChangeId(swagger, builder, path.getPut().getResponses());
        }*/
        if(null!= path.getPatch()){
            getParAndChangeId(swagger, builder, path.getPatch().getResponses());
        }

        return builder.toString();
    }

    private static void postAndPutModelDoc(Swagger swagger, StringBuilder builder, List<Parameter> parameters) {
        AtomicReference<Model> backModel = new AtomicReference<>();

        if(parameters != null){
            parameters.forEach(parameter -> {
                if(parameter instanceof BodyParameter){
                    BodyParameter bop = (BodyParameter)parameter;

                    if(bop.getSchema() instanceof RefModel){
                        RefModel refModel = (RefModel)bop.getSchema();
                        String simpleRef = refModel.getSimpleRef();
                        System.err.println(simpleRef);

                        Model model1 = definitions.get(simpleRef);
                        String s = SwaggerUtils.definitionsDocumentGenerateMd(swagger, simpleRef, model1);
                        builder.append(StringUtils.replace(s, "name=", "id="));
                        modelDoc(swagger,builder,model1);
                    }

                    if(bop.getSchema() instanceof ModelImpl){
                        ModelImpl model = (ModelImpl)bop.getSchema();
                        modelDocAddJdd(swagger,builder,backModel,model.getProperties());
                    }


                    if(bop.getSchema() instanceof ArrayModel){
                        ArrayModel arrayModel = (ArrayModel)bop.getSchema();
                        //Property items = arrayModel.getItems();
                        modelDocAdd(swagger,builder,backModel,arrayModel.getItems());

                        if(null != backModel.get()){
                            modelDoc(swagger,builder,backModel.get());
                        }
                    }

                }
            });
        }
    }

    private static void getParAndChangeId(Swagger swagger, StringBuilder builder, Map<String, Response> responses) {
        if(null!= responses){
            responses.forEach((k,response)-> modelDoc(swagger, builder, response.getResponseSchema()));
        }
    }


    /**
     * 递归处理参数模型
     * @param swagger
     * @param builder
     * @param model
     */
    private static void modelDoc(Swagger swagger, StringBuilder builder , Model model){
        AtomicReference<Model> backModel = new AtomicReference<>();

        if (null != model){
            if(model instanceof RefModel){
                RefModel refModel = (RefModel)model;

                Map<String, Property> properties = refModel.getProperties();
                String simpleRef = refModel.getSimpleRef();

                if( null == properties && StringUtils.isNotBlank(simpleRef)){

                    backModel.set(SwaggerUtils.definitions.get(simpleRef));

                    String s = SwaggerUtils.definitionsDocumentGenerateMd(swagger, simpleRef, backModel.get());
                    builder.append(StringUtils.replace(s, "name=", "id="));

                    if(null != backModel.get()){
                        modelDoc(swagger,builder,backModel.get());
                    }
                }

            }

            if(model instanceof ComposedModel){
                ComposedModel composedModel = (ComposedModel)model;
                //composedModel.getProperties();
                List<Model> allOfModel = composedModel.getAllOf();
                allOfModel.forEach(oneOfModel -> modelDoc(swagger,builder,oneOfModel));

            }

            if(model instanceof ArrayModel){
                ArrayModel arrayModel = (ArrayModel)model;
                modelDocAdd(swagger,builder,backModel,arrayModel.getItems());
            }

            Map<String, Property> properties = model.getProperties();

            modelDocAddJdd(swagger,builder,backModel,properties);
        }
    }

    private static void modelDocAdd(Swagger swagger, StringBuilder builder, AtomicReference<Model> backModel, Property property) {
        if(property instanceof RefProperty){
            RefProperty refProperty = (RefProperty)property;
            String simpleRef = refProperty.getSimpleRef();

            backModel.set(SwaggerUtils.definitions.get(simpleRef));
            String modelDoc = SwaggerUtils.definitionsDocumentGenerateMd(swagger, simpleRef, backModel.get());
            builder.append(StringUtils.replace(modelDoc, "name=", "id="));
        }
    }

    private static void modelDocAddJdd(Swagger swagger, StringBuilder builder, AtomicReference<Model> backModel, Map<String, Property> properties){
        if(null != properties){
            properties.forEach((zd,property)->{

                modelDocAdd(swagger, builder, backModel, property);
                if(null != backModel.get()){
                    modelDoc(swagger,builder,backModel.get());
                }

                if(property instanceof ObjectProperty){
                    ObjectProperty objectProperty = (ObjectProperty)property;
                    Map<String, Property> ObjectProperties = objectProperty.getProperties();
                    modelDocAddJdd(swagger,builder,backModel,ObjectProperties);
                }

                if(property instanceof ArrayProperty){
                    ArrayProperty arrayProperty = (ArrayProperty)property;
                    Property items = arrayProperty.getItems();
                    modelDocAdd(swagger, builder, backModel, items);

                    if(null != backModel.get()){
                        modelDoc(swagger,builder,backModel.get());
                    }
                }



            });
        }
    }


    public static String generateMd(Swagger swagger, String k, Path swaggerPath){
        StringBuilder builder = new StringBuilder();

        Map<String, Path> paths = new HashMap<>();
        paths.put(k,swaggerPath);
        swagger.setPaths(paths);

        Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build();
        Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext();
        XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context);

        builder.append(xforcceSwagger2MarkupConverter.toString()+"\n" +
                "#### 在线调试" + "\n" + "[模拟调用]("+
                swaggerUiUrl + getExternalDocs(swaggerPath) + ")");

        String s = SwaggerUtils.doPathWithParameter(swagger, swaggerPath);
        builder.append(s);
        /*if(swaggerPath.getPost() != null){
            String s = SwaggerUtils.doPathWithParameter(swagger, swaggerPath);
            builder.append(s);
        }*/
        return builder.toString();
    }

    public static String overviewDocumentGenerateMd(Swagger swagger){
            Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build();
            Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext();
            XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context);
            return xforcceSwagger2MarkupConverter.overviewDocumenttoString();
    }

    public static String securityDocumentGenerateMd(Swagger swagger){
        Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build();
        Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext();
        XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context);
        return xforcceSwagger2MarkupConverter.securityDocumenttoString();
    }

    public static String definitionsDocumentGenerateMd(Swagger swagger,String modelKey,Model model){
        Map<String, Model> definitions = new HashMap<>();
        definitions.put(modelKey,model);
        swagger.setDefinitions(definitions);


        Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build();
        Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext();
        XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context);
        return xforcceSwagger2MarkupConverter.definitionsDocumenttoString();
    }


    private static void findApiByTag(Path path, Tag tag, List<Path> showDocPath){
        addShowDocByTag(path.getPost(),path,tag,showDocPath);
        addShowDocByTag(path.getGet(),path,tag,showDocPath);
        addShowDocByTag(path.getDelete(),path,tag,showDocPath);
        addShowDocByTag(path.getPut(),path,tag,showDocPath);
        addShowDocByTag(path.getOptions(),path,tag,showDocPath);
        addShowDocByTag(path.getHead(),path,tag,showDocPath);
        addShowDocByTag(path.getPatch(),path,tag,showDocPath);
    }

    private static List<Path> addShowDocByTag(Operation operation, Path path, Tag tag, List<Path> showDocPath){
        if(null!= operation && null != operation.getTags()){
            List<String> tagList = operation.getTags();

            tagList.forEach(tags ->{
                if(StringUtils.equals(tag.getName(),tags)){
                    showDocPath.add(path);
                }
            });
        }
        return showDocPath;
    }

    private static String getApiDescription(Path path){
        if(null!= path.getPost()){
            return path.getPost().getSummary();
        }
        if(null!= path.getGet()){
            return path.getGet().getSummary();
        }
        if(null!= path.getHead()){
            return path.getHead().getSummary();
        }
        if(null!= path.getDelete()){
            return path.getDelete().getSummary();
        }
        if(null!= path.getOptions()){
            return path.getOptions().getSummary();
        }
        if(null!= path.getPut()){
            return path.getPut().getSummary();
        }
        if(null!= path.getPatch()){
            return path.getPatch().getSummary();
        }
        return "";
    }


    public static void doShowDoc(Path path, Tag tag, Swagger swagger, String api_path,String api_key,String api_token){
        if(null!= path.getPost() && path.getPost().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
        if(null!= path.getGet() && path.getGet().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
        if(null!= path.getPut() && path.getPut().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
        if(null!= path.getOptions() && path.getOptions().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
        if(null!= path.getDelete() && path.getDelete().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
        if(null!= path.getPatch() && path.getPatch().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
        if(null!= path.getHead() && path.getHead().getTags().contains(tag.getName())){
            sendToShowDoc(api_key,api_token,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,api_path,path),"");
        }
    }


    private static void sendToShowDoc(String api_key , String api_token,String cat_name, String cat_name_sub, String page_title, String page_content, String s_number){
        Map<String,String> parMap = new HashMap<>();
        parMap.put("api_key",api_key);
        parMap.put("api_token",api_token);
        parMap.put("cat_name",cat_name);
        parMap.put("cat_name_sub",cat_name_sub);
        parMap.put("page_title",page_title);
        parMap.put("page_content",page_content);
        parMap.put("s_number",s_number);
        okHttpUtil.post(showDocUrl,parMap);
    }

    public static void updateToShowDoc(List ignoreApis) throws IOException {
        //TODO 增加排除特定接口后导入showDoc
    }


    public static void updateToShowDoc(String api_key, String api_token, Swagger swagger) {
        SwaggerUtils.definitions = swagger.getDefinitions();
        Map<String,List<Path>> showDocMap = new HashMap<>();

        /*ObjectMapper mapper = new ObjectMapper();
        URL url = SwaggerUtils.class.getClassLoader().getResource("public/swagger/api.json");
        JsonNode jsonNode = mapper.readValue(url, JsonNode.class);
        Swagger swagger = new SwaggerParser().read(jsonNode);*/

        /**
         * 接口列表
         */

        List<Tag> tags = swagger.getTags();
        Map<String, Path> paths = swagger.getPaths();

        tags.forEach( tag -> {
            List<Path> showDocPath = new ArrayList<>();
            paths.forEach((k,v) ->{
                findApiByTag(v, tag, showDocPath);
                doShowDoc(v,tag,swagger,k,api_key,api_token);
            });
            showDocMap.put(tag.getDescription(),showDocPath);
        });

        /**
         * 数据模型
         */
        if(null != SwaggerUtils.definitions){
            SwaggerUtils.definitions.forEach((k,v) -> sendToShowDoc(api_key,api_token,"数据模型","",k,SwaggerUtils.definitionsDocumentGenerateMd(swagger,k,v),""));
        }
                /**
         * 概览
         */
        sendToShowDoc(api_key,api_token,"","","概览",SwaggerUtils.overviewDocumentGenerateMd(swagger),"");
        /**
         * 安全
         */
        sendToShowDoc(api_key,api_token,"","","安全",SwaggerUtils.securityDocumentGenerateMd(swagger),"");

    }
}
